package com.campuscarbooking.api.service.impl;

import com.campuscarbooking.api.annotation.LogExecutionTime;
import com.campuscarbooking.api.annotation.LogMethod;
import com.campuscarbooking.api.model.BusSchedule;
import com.campuscarbooking.api.model.Reservation;
import com.campuscarbooking.api.service.SeatCacheService;
import com.campuscarbooking.api.util.RedisLockUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * 车位预约缓存服务实现类
 * 使用Redis读写锁实现强一致性
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class SeatCacheServiceImpl implements SeatCacheService {

    private final RedisTemplate<String, Object> redisTemplate;
    private final RedisLockUtil redisLockUtil;
    
    // 缓存前缀
    private static final String SCHEDULE_SEATS_PREFIX = "schedule:seats:";
    private static final String SCHEDULE_RESERVED_SEATS_PREFIX = "schedule:reserved:";
    private static final String USER_RESERVATION_PREFIX = "user:reservation:";
    private static final String SCHEDULE_SEAT_COUNT_PREFIX = "schedule:seat:count:";
    
    // 缓存过期时间，单位秒
    private static final long CACHE_EXPIRE = 24 * 60 * 60; // 1天
    
    @Override
    @LogMethod("获取班车可用座位")
    @LogExecutionTime
    public Set<Integer> getAvailableSeats(Long scheduleId) {
        String cacheKey = SCHEDULE_SEATS_PREFIX + scheduleId;
        
        // 获取读锁
        try {
            if (!redisLockUtil.tryReadLock(cacheKey)) {
                log.warn("获取班车可用座位读锁失败, scheduleId: {}", scheduleId);
                return new HashSet<>();
            }
            
            // 从缓存中获取可用座位
            Set<Object> availableSeats = redisTemplate.opsForSet().members(cacheKey);
            if (availableSeats == null || availableSeats.isEmpty()) {
                return new HashSet<>();
            }
            
            return availableSeats.stream()
                    .map(seat -> (Integer) seat)
                    .collect(Collectors.toSet());
        } finally {
            // 释放读锁
            redisLockUtil.releaseReadLock(cacheKey);
        }
    }

    @Override
    @LogMethod("占用班车座位")
    @LogExecutionTime
    public boolean occupySeat(Long scheduleId, Integer seatNumber) {
        String cacheKey = SCHEDULE_SEATS_PREFIX + scheduleId;
        String reservedSeatsKey = SCHEDULE_RESERVED_SEATS_PREFIX + scheduleId;
        String seatCountKey = SCHEDULE_SEAT_COUNT_PREFIX + scheduleId;
        
        // 获取写锁
        try {
            if (!redisLockUtil.tryWriteLock(cacheKey)) {
                log.warn("获取班车座位写锁失败, scheduleId: {}, seatNumber: {}", scheduleId, seatNumber);
                return false;
            }
            
            // 检查座位是否可用
            Boolean isMember = redisTemplate.opsForSet().isMember(cacheKey, seatNumber);
            if (isMember == null || !isMember) {
                log.warn("座位不可用, scheduleId: {}, seatNumber: {}", scheduleId, seatNumber);
                return false;
            }
            
            // 从可用座位集合中移除
            redisTemplate.opsForSet().remove(cacheKey, seatNumber);
            // 添加到已预约座位集合
            redisTemplate.opsForSet().add(reservedSeatsKey, seatNumber);
            // 更新已预约座位数量
            redisTemplate.opsForValue().increment(seatCountKey, 1);
            
            return true;
        } finally {
            // 释放写锁
            redisLockUtil.releaseWriteLock(cacheKey);
        }
    }

    @Override
    @LogMethod("释放班车座位")
    @LogExecutionTime
    public boolean releaseSeat(Long scheduleId, Integer seatNumber) {
        String cacheKey = SCHEDULE_SEATS_PREFIX + scheduleId;
        String reservedSeatsKey = SCHEDULE_RESERVED_SEATS_PREFIX + scheduleId;
        String seatCountKey = SCHEDULE_SEAT_COUNT_PREFIX + scheduleId;
        
        // 获取写锁
        try {
            if (!redisLockUtil.tryWriteLock(cacheKey)) {
                log.warn("获取班车座位写锁失败, scheduleId: {}, seatNumber: {}", scheduleId, seatNumber);
                return false;
            }
            
            // 检查座位是否已预约
            Boolean isMember = redisTemplate.opsForSet().isMember(reservedSeatsKey, seatNumber);
            if (isMember == null || !isMember) {
                log.warn("座位未被预约, scheduleId: {}, seatNumber: {}", scheduleId, seatNumber);
                return false;
            }
            
            // 从已预约座位集合中移除
            redisTemplate.opsForSet().remove(reservedSeatsKey, seatNumber);
            // 添加到可用座位集合
            redisTemplate.opsForSet().add(cacheKey, seatNumber);
            // 更新已预约座位数量
            redisTemplate.opsForValue().decrement(seatCountKey, 1);
            
            return true;
        } finally {
            // 释放写锁
            redisLockUtil.releaseWriteLock(cacheKey);
        }
    }

    @Override
    @LogMethod("初始化班车座位")
    @LogExecutionTime
    public void initializeSeats(BusSchedule schedule) {
        String cacheKey = SCHEDULE_SEATS_PREFIX + schedule.getId();
        String reservedSeatsKey = SCHEDULE_RESERVED_SEATS_PREFIX + schedule.getId();
        String seatCountKey = SCHEDULE_SEAT_COUNT_PREFIX + schedule.getId();
        
        // 获取写锁
        try {
            if (!redisLockUtil.tryWriteLock(cacheKey)) {
                log.warn("获取班车座位写锁失败, scheduleId: {}", schedule.getId());
                return;
            }
            
            // 清空已有的座位缓存
            redisTemplate.delete(cacheKey);
            redisTemplate.delete(reservedSeatsKey);
            
            // 初始化可用座位集合
            Set<Integer> availableSeats = IntStream.rangeClosed(1, schedule.getCapacity())
                    .boxed()
                    .collect(Collectors.toSet());
            
            redisTemplate.opsForSet().add(cacheKey, 
                    availableSeats.toArray(new Integer[0]));
            
            // 设置初始预约座位数量为0
            redisTemplate.opsForValue().set(seatCountKey, 0);
            
            // 设置缓存过期时间
            redisTemplate.expire(cacheKey, CACHE_EXPIRE, TimeUnit.SECONDS);
            redisTemplate.expire(reservedSeatsKey, CACHE_EXPIRE, TimeUnit.SECONDS);
            redisTemplate.expire(seatCountKey, CACHE_EXPIRE, TimeUnit.SECONDS);
        } finally {
            // 释放写锁
            redisLockUtil.releaseWriteLock(cacheKey);
        }
    }

    @Override
    @LogMethod("获取用户预约")
    @LogExecutionTime
    public Long getUserReservation(Long scheduleId, Long userId) {
        String cacheKey = USER_RESERVATION_PREFIX + userId + ":" + scheduleId;
        
        // 获取读锁
        try {
            if (!redisLockUtil.tryReadLock(cacheKey)) {
                log.warn("获取用户预约读锁失败, scheduleId: {}, userId: {}", scheduleId, userId);
                return null;
            }
            
            Object reservationId = redisTemplate.opsForValue().get(cacheKey);
            return reservationId == null ? null : (Long) reservationId;
        } finally {
            // 释放读锁
            redisLockUtil.releaseReadLock(cacheKey);
        }
    }

    @Override
    @LogMethod("设置用户预约")
    @LogExecutionTime
    public void setUserReservation(Reservation reservation) {
        Long scheduleId = reservation.getSchedule().getId();
        Long userId = reservation.getUser().getId();
        String cacheKey = USER_RESERVATION_PREFIX + userId + ":" + scheduleId;
        
        // 获取写锁
        try {
            if (!redisLockUtil.tryWriteLock(cacheKey)) {
                log.warn("获取用户预约写锁失败, scheduleId: {}, userId: {}", scheduleId, userId);
                return;
            }
            
            // 设置用户预约
            redisTemplate.opsForValue().set(cacheKey, reservation.getId(), CACHE_EXPIRE, TimeUnit.SECONDS);
        } finally {
            // 释放写锁
            redisLockUtil.releaseWriteLock(cacheKey);
        }
    }

    @Override
    @LogMethod("删除用户预约")
    @LogExecutionTime
    public void removeUserReservation(Long scheduleId, Long userId) {
        String cacheKey = USER_RESERVATION_PREFIX + userId + ":" + scheduleId;
        
        // 获取写锁
        try {
            if (!redisLockUtil.tryWriteLock(cacheKey)) {
                log.warn("获取用户预约写锁失败, scheduleId: {}, userId: {}", scheduleId, userId);
                return;
            }
            
            // 删除用户预约
            redisTemplate.delete(cacheKey);
        } finally {
            // 释放写锁
            redisLockUtil.releaseWriteLock(cacheKey);
        }
    }

    @Override
    @LogMethod("获取已预约座位数量")
    @LogExecutionTime
    public int getReservedSeatsCount(Long scheduleId) {
        String seatCountKey = SCHEDULE_SEAT_COUNT_PREFIX + scheduleId;
        
        // 获取读锁
        try {
            if (!redisLockUtil.tryReadLock(seatCountKey)) {
                log.warn("获取已预约座位数量读锁失败, scheduleId: {}", scheduleId);
                return 0;
            }
            
            Object count = redisTemplate.opsForValue().get(seatCountKey);
            return count == null ? 0 : ((Number) count).intValue();
        } finally {
            // 释放读锁
            redisLockUtil.releaseReadLock(seatCountKey);
        }
    }

    @Override
    @LogMethod("检查座位是否可用")
    @LogExecutionTime
    public boolean isSeatAvailable(Long scheduleId, Integer seatNumber) {
        String cacheKey = SCHEDULE_SEATS_PREFIX + scheduleId;
        
        // 获取读锁
        try {
            if (!redisLockUtil.tryReadLock(cacheKey)) {
                log.warn("获取班车座位读锁失败, scheduleId: {}, seatNumber: {}", scheduleId, seatNumber);
                return false;
            }
            
            Boolean isMember = redisTemplate.opsForSet().isMember(cacheKey, seatNumber);
            return isMember != null && isMember;
        } finally {
            // 释放读锁
            redisLockUtil.releaseReadLock(cacheKey);
        }
    }
} 